#include "device.h"

#include <tchar.h>

#define ERR(s, ...) _tprintf(TEXT(s), __VA_ARGS__)

windows::user::Device::Device() : device_handle_(INVALID_HANDLE_VALUE) {
  RtlZeroMemory(file_name_, sizeof(file_name_));
}

windows::user::Device::~Device() { Close(); }

DWORD windows::user::Device::Open(LPCTSTR file_name, BOOL asynchronous) {
  DWORD error;
  SIZE_T required_size;

  if (!file_name) {
    ERR("file_name=%p\n", file_name);
    return ERROR_INVALID_PARAMETER;
  }

  Close();
  required_size = sizeof(file_name_);
  error = FormatSymbolicLink(file_name, file_name_, required_size);
  if (ERROR_SUCCESS != error) {
    ERR("FormatSymbolicLink fail, file_name=%s, required_size=%llu\n",
        file_name, required_size);
    return ERROR_INVALID_NAME;
  }

  if (asynchronous) {
    device_handle_ = CreateFile(
        file_name_, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
  } else {
    device_handle_ =
        CreateFile(file_name_, GENERIC_READ | GENERIC_WRITE, 0, NULL,
                   OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  }

  if (INVALID_HANDLE_VALUE == device_handle_) {
    error = GetLastError();
    ERR("CreateFile fail, lpFileName=%s, error=%d\n", file_name_, error);
    return error;
  }
  return ERROR_SUCCESS;
}

VOID windows::user::Device::Close() {
  if (INVALID_HANDLE_VALUE != device_handle_) {
    CloseHandle(device_handle_);
    device_handle_ = INVALID_HANDLE_VALUE;
  }
  RtlZeroMemory(file_name_, sizeof(file_name_));
}

DWORD windows::user::Device::Read(PVOID buf, DWORD &size) {
  DWORD error;
  DWORD read_size;
  if (INVALID_HANDLE_VALUE == device_handle_) {
    return ERROR_INVALID_HANDLE;
  }
  error = ERROR_SUCCESS;
  if (!ReadFile(device_handle_, buf, size, &read_size, NULL)) {
    error = GetLastError();
    ERR("ReadFile fail, device_handle_=%p, buf=%p, size=%d, read_size=%d "
        "error=%d\n",
        device_handle_, buf, size, read_size, error);
  }
  return error;
}

DWORD windows::user::Device::ReadAsync(PVOID buf, DWORD &size,
                                       OVERLAPPED &overlapped) { 
  DWORD error;
  DWORD read_size;
  
  if (INVALID_HANDLE_VALUE == device_handle_) {
    return ERROR_INVALID_HANDLE;
  }
  error = ERROR_SUCCESS;
  if (!ReadFile(device_handle_, buf, size, &read_size, &overlapped)) {
    error = GetLastError();
    if (ERROR_IO_PENDING != error) {
      ERR("ReadFile fail, device_handle_=%p, buf=%p, size=%d, read_size=%d error=%d\n",
        device_handle_, buf, size, read_size, error);
    }
  }
  return error;
}

DWORD windows::user::Device::Write(PVOID buf, DWORD &size) {
  DWORD error;
  DWORD written_size;
  if (INVALID_HANDLE_VALUE == device_handle_) {
    return ERROR_INVALID_HANDLE;
  }
  error = ERROR_SUCCESS;
  if (!WriteFile(device_handle_, buf, size, &written_size, NULL)) {
    error = GetLastError();
    ERR("WriteFile fail, device_handle_=%p, buf=%p, size=%d, written_size=%d "
        "error=%d\n",
        device_handle_, buf, size, written_size, error);
  }
  return error;
}

DWORD windows::user::Device::Control(DWORD control_code, PVOID input_buffer,
                                     ULONG input_buffer_size,
                                     PVOID output_buffer,
                                     ULONG output_buffer_size,
                                     PDWORD returned_bytes) {
  DWORD error = GetLastError();
  DWORD returned_length;
  if (INVALID_HANDLE_VALUE == device_handle_) {
    return ERROR_INVALID_HANDLE;
  }
  error = ERROR_SUCCESS;
  if (!DeviceIoControl(device_handle_, control_code, input_buffer,
                       input_buffer_size, output_buffer, output_buffer_size,
                       &returned_length, NULL)) {
    error = GetLastError();
    ERR("DeviceIoControl fail, device_handle_=%p, control_code=%d, "
        "input_buffer=%p, input_buffer_size=%d, output_buffer=%p, "
        "output_buffer_size=%d, returned_length=%d, error=%d\n",
        device_handle_, control_code, input_buffer, input_buffer_size,
        output_buffer, output_buffer_size, returned_length, error);
  }
  if (returned_bytes) {
    *returned_bytes = returned_length;
  }
  return error;
}

DWORD windows::user::Device::FormatSymbolicLink(LPCTSTR file_name,
                                                LPTSTR standard_symbolic_link,
                                                SIZE_T &size) {
  PCTSTR prefix = TEXT("\\\\.\\");
  CONST SIZE_T prefix_length = _tcslen(prefix);
  SIZE_T required_size;

  if (0 != _tcsncmp(file_name, prefix, prefix_length)) {
    required_size = (prefix_length + _tcslen(file_name) +
                     sizeof(TEXT('\0')) / sizeof(TCHAR)) *
                    sizeof(TCHAR);
    if (size < required_size) {
      size = required_size;
      return ERROR_INSUFFICIENT_BUFFER;
    } else {
      RtlZeroMemory(standard_symbolic_link, required_size);
      _tcsncpy_s(standard_symbolic_link, size / sizeof(TCHAR), prefix,
                 _tcslen(prefix));
      _tcsncat_s(standard_symbolic_link, size / sizeof(TCHAR), file_name,
                 _tcslen(file_name));
      size = required_size;
      return ERROR_SUCCESS;
    }
  } else {
    required_size = (_tcslen(file_name) + sizeof(TEXT('\0')) / sizeof(TCHAR)) *
                    sizeof(TCHAR);
    if (size < required_size) {
      size = required_size;
      return ERROR_INSUFFICIENT_BUFFER;
    } else {
      RtlZeroMemory(standard_symbolic_link, required_size);
      _tcsncpy_s(standard_symbolic_link, size, file_name, _tcslen(file_name));
      size = required_size;
      return ERROR_SUCCESS;
    }
  }
}
